package com.roundarch.www.util.propertyeditor;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.util.StringUtils;

import com.roundarch.www.util.CollectionUtils;


@SuppressWarnings("rawtypes")
public class CollectionPropertyEditor extends PropertyEditorSupport
{
    public static final String DEFAULT_DELIMITERS = ",";

    private static Map<Class<? extends Collection>, Class<? extends Collection>> DEFAULT_INTERFACE_IMPLEMENTATIONS = new HashMap<Class<? extends Collection>, Class<? extends Collection>>();

    static
    {
        DEFAULT_INTERFACE_IMPLEMENTATIONS.put(Collection.class, ArrayList.class);
        DEFAULT_INTERFACE_IMPLEMENTATIONS.put(List.class, ArrayList.class);
        DEFAULT_INTERFACE_IMPLEMENTATIONS.put(Set.class, HashSet.class);
        DEFAULT_INTERFACE_IMPLEMENTATIONS.put(SortedSet.class, TreeSet.class);
        DEFAULT_INTERFACE_IMPLEMENTATIONS.put(Queue.class, LinkedList.class);
    }

    private Class<? extends Collection> _collectionType;
    private PropertyEditor _itemEditor = new StringTrimmerEditor(true);
    private boolean _includeNulls = false;
    private String _delimiters = DEFAULT_DELIMITERS;

    /**
     * Defaults to using a string trimming property editor.
     */
    public CollectionPropertyEditor(Class<? extends Collection> collectionType)
    {
        _collectionType = collectionType.isInterface() ? DEFAULT_INTERFACE_IMPLEMENTATIONS.get(collectionType) : collectionType;
    }

    /**
     * Defaults to using a string trimming property editor (which returns empty strings as null).
     */
    public CollectionPropertyEditor(Class<? extends Collection> collectionType, String delimiters)
    {
        _collectionType = collectionType.isInterface() ? DEFAULT_INTERFACE_IMPLEMENTATIONS.get(collectionType) : collectionType;
        _delimiters = delimiters;
    }

    public CollectionPropertyEditor(Class<? extends Collection> collectionType, PropertyEditor itemEditor)
    {
        this(collectionType);
        _itemEditor = itemEditor;
    }

    public CollectionPropertyEditor(Class<? extends Collection> collectionType, PropertyEditor itemEditor, boolean includeNulls)
    {
        this(collectionType, itemEditor);
        _includeNulls = includeNulls;
    }

    /**
     * Converts a Collection into a delimited String.
     */
    @Override
    public String getAsText()
    {
        Collection values = (Collection) getValue();
        StringBuffer textBuffer = new StringBuffer();

        if (values == null)
            return textBuffer.toString();

        for (Iterator i = values.iterator(); i.hasNext();)
        {
            Object item = (Object) i.next();
            _itemEditor.setValue(item);
            textBuffer.append(_itemEditor.getAsText());
            if (i.hasNext())
                textBuffer.append(_delimiters.charAt(0));
        }
        return textBuffer.toString();
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setAsText(String text) throws IllegalArgumentException
    {
        Collection currentValues = (Collection) getValue();
        if (currentValues == null)
        {
            currentValues = createCollection();
            setValue(currentValues);
        }

        String[] values = StringUtils.tokenizeToStringArray(text, _delimiters, true, true);
        Collection newValues = createCollection();

        for (String value : values)
        {
            _itemEditor.setAsText(value);

            Object objectValue = _itemEditor.getValue();
            if (objectValue != null || _includeNulls)
                newValues.add(objectValue);
        }

        CollectionUtils.merge(currentValues, newValues);
    }

    @Override
    public void setValue(Object value)
    {
        super.setValue(value);
    }

    @Override
    public Object getValue()
    {
        return super.getValue();
    }

    @SuppressWarnings("unchecked")
    protected Collection<Object> createCollection()
    {
        return (Collection<Object>) BeanUtils.instantiateClass(getCollectionType());
    }

    protected Class<? extends Collection> getCollectionType()
    {
        return _collectionType;
    }
}
